package cn.mazexal.examapi.services;

import cn.hutool.core.util.IdUtil;
import cn.mazexal.examapi.common.models.BizException;
import cn.mazexal.examapi.common.models.CustomerError;
import cn.mazexal.examapi.common.models.PagedData;
import cn.mazexal.examapi.dataware.ExamPageMapper;
import cn.mazexal.examapi.dataware.UserExamLogMapper;
import cn.mazexal.examapi.models.converter.ExamPageConverter;
import cn.mazexal.examapi.models.db.ExamPageDO;
import cn.mazexal.examapi.models.db.ExamQuestionDO;
import cn.mazexal.examapi.models.db.UserExamLogDO;
import cn.mazexal.examapi.models.db.UserQuestionLogDO;
import cn.mazexal.examapi.models.enums.ExamStatus;
import cn.mazexal.examapi.models.enums.QuestionTypeEnum;
import cn.mazexal.examapi.models.form.CommitExamForm;
import cn.mazexal.examapi.models.form.CommitExamItem;
import cn.mazexal.examapi.models.qry.ExamDetailQry;
import cn.mazexal.examapi.models.qry.ExamPageListQry;
import cn.mazexal.examapi.models.vo.*;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author: Mazexal
 * Date: 2025/5/11
 */
@Service
public class ExamService {
    @Resource
    private ExamPageMapper examPageMapper;

    @Resource
    private ExamPageConverter examPageConverter;

    @Resource
    private QuestionService questionService;

    @Resource
    private UserExamLogMapper userExamLogMapper;

    public ExamPageVO getRealExamDetail(ExamDetailQry qry) {
        ExamPageDO examPageDO = examPageMapper.selectById(qry.getId());
        if (examPageDO == null) {
            throw new BizException(new CustomerError("500", "该考试不存在"));
        }
        ExamPageVO examPageVO = examPageConverter.toView(examPageDO);
        // 装载数据
        List<ExamQuestionVO> questionList = questionService.getQuestionListByExamId(qry.getId(), qry.getUserId(),
                qry.getExamBankId());
        List<Long> materialIds = questionList.stream().map(ExamQuestionVO::getMaterialsId).filter(x -> x > 0).toList();
        List<ExamQuestionMaterialsVO> materialsList = questionService.getExamQuestionMaterialsList(materialIds);
        examPageVO.setQuestionList(questionList);
        examPageVO.setMaterialsList(materialsList);
        return examPageVO;
    }

    public PagedData<ExamPageVO> getRealExamList(ExamPageListQry qry) {
        List<ExamPageDO> list = examPageMapper.getRealExamList(qry);
        int count = examPageMapper.getRealExamListCount(qry);
        List<ExamPageVO> res = list.stream().map(examPageConverter::toView).toList();
        return PagedData.of(qry, count, res);
    }

    @Transactional(rollbackFor = Exception.class)
    public ExamResultVO commitExam(CommitExamForm form) {
        ExamResultVO res = new ExamResultVO();
        UserExamLogDO userExamLogDO = userExamLogMapper.selectById(form.getUserExamId());
        if (userExamLogDO == null) {
            throw new BizException(new CustomerError("500", "考试不存在"));
        }
        if (userExamLogDO.getExamStatus() != ExamStatus.DOING.getCode()) {
            throw new BizException(new CustomerError("500", "考试已结束"));
        }

        List<ExamQuestionDO> wrongQuestionList = new ArrayList<>();
        List<Long> questionIds = new ArrayList<>();
        // 插入user_question_log
        form.getUserAnswerList().forEach(item -> {
            questionIds.add(item.getQuestionId());
        });
        //这里逻辑是从试卷获取题目, 而不是从提交的表单获取题目
        List<ExamQuestionDO> questionList = questionService.getExamQuestionListByExamId(userExamLogDO.getExamId());

        Map<Long, CommitExamItem> userAnswerMap = form.getUserAnswerList().stream().collect(HashMap::new,
                (k, v) -> k.put(v.getQuestionId(), v), HashMap::putAll);
        BigDecimal score = BigDecimal.ZERO;
        int rightNum = 0;
        int wrongNum = 0;
        int unAnswerNum = 0;
        List<UserQuestionLogDO> questionLogDOList = new ArrayList<>();
        for (ExamQuestionDO item : questionList) {
            CommitExamItem commitExamItem = userAnswerMap.get(item.getId());
            BigDecimal questionScore = BigDecimal.ZERO;
            boolean isCorrect = false;
            if (item.getQuestionType() == QuestionTypeEnum.SINGLE_CHOICE.getCode() || item.getQuestionType() == QuestionTypeEnum.MULTIPLE_CHOICE.getCode()) {
                if (commitExamItem == null || StringUtils.isBlank(commitExamItem.getUserAnswer())) {
                    // 如果为空, 说明未答题
                    wrongQuestionList.add(item);
                    unAnswerNum++;
                } else {
                    isCorrect = questionService.checkAnswer(commitExamItem.getUserAnswer(), item);
                    if (!isCorrect) {
                        wrongQuestionList.add(item);
                        wrongNum++;
                    } else {
                        questionScore = item.getScore();
                        score = score.add(item.getScore());
                        rightNum++;
                    }
                }
            }
            questionLogDOList.add(buildUserQuestionLogDO(IdUtil.getSnowflakeNextId(), userExamLogDO.getUserId(),
                    userExamLogDO.getExamBankId(), userExamLogDO.getExamId(), item.getId(), item.getMenuId(),
                    commitExamItem == null ? "" : commitExamItem.getUserAnswer(), questionScore,
                    commitExamItem == null ? 0L : commitExamItem.getUseTime(), userExamLogDO.getId()));
        }
        // 如果有错题, 还需要插入user_wrong_question
        if (CollectionUtils.isNotEmpty(wrongQuestionList)) {
            questionService.addUserWrongQuestion(userExamLogDO.getExamBankId(), userExamLogDO.getUserId(),
                    wrongQuestionList);
        }

        questionService.addUserQuestionLogList(questionLogDOList);

        // 更改user_exam_log.的花费时间,结束时间,分数,状态, 正确数, 错误数
        userExamLogDO.setCostTime(form.getCostTime());
        userExamLogDO.setEndTime(System.currentTimeMillis());
        userExamLogDO.setScore(score);
        userExamLogDO.setExamStatus(ExamStatus.FINISH.getCode());
        userExamLogDO.setRightNum(rightNum);
        userExamLogDO.setWrongNum(wrongNum);
        userExamLogDO.setUnAnswerNum(unAnswerNum);
        userExamLogMapper.updateById(userExamLogDO);
        res.setScore(score);
        res.setRightNum(rightNum);
        res.setWrongNum(wrongNum);
        res.setUseTime(form.getCostTime());
        res.setUnAnswerNum(unAnswerNum);
        return res;

    }

    UserQuestionLogDO buildUserQuestionLogDO(Long id, Long userId, Long examBankId, Long examId, Long questionId,
                                             Long menuId, String answer, BigDecimal score, Long useTime,
                                             Long logId) {
        UserQuestionLogDO userQuestionLogDO = new UserQuestionLogDO();
        userQuestionLogDO.setCreateTime(System.currentTimeMillis());
        userQuestionLogDO.setId(id);
        userQuestionLogDO.setUserId(userId);
        userQuestionLogDO.setExamBankId(examBankId);
        userQuestionLogDO.setExamId(examId);
        userQuestionLogDO.setQuestionId(questionId);
        userQuestionLogDO.setMenuId(menuId);
        userQuestionLogDO.setAnswer(answer);
        userQuestionLogDO.setScore(score);
        userQuestionLogDO.setUseTime(useTime);
        userQuestionLogDO.setLogId(logId);
        return userQuestionLogDO;
    }

    public void wrapUserExamLog(UserExamQuestionLogWithMaterialsVO res) {
        UserExamLogDO userExamLogDO = userExamLogMapper.selectById(res.getLogId());
        ExamPageDO examPageDO = examPageMapper.selectById(userExamLogDO.getExamId());
        res.setExamId(userExamLogDO.getExamId());
        res.setScore(userExamLogDO.getScore());
        res.setUseTime(userExamLogDO.getCostTime());
        res.setRightNum(userExamLogDO.getRightNum());
        res.setWrongNum(userExamLogDO.getWrongNum());
        res.setUnAnswerNum(userExamLogDO.getUnAnswerNum());
        res.setCommitTime(userExamLogDO.getEndTime());
        res.setTitle(examPageDO.getName());
        res.setExtraInfo(examPageDO.getExtraInfo());
    }
}
